package ee122Networking;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;


public class ServerReceiverThread extends Thread {
	protected DatagramSocket socket = null;
	
	public int numPacketsReceived;
	public String name;
	public FileOutputStream gif= null;
	
	public ArrayList<TwoHopPacket> receivedPackets = new ArrayList<TwoHopPacket>(1000);
	public LinkedList<Integer> missedPackets = new LinkedList<Integer>();
	
	private int lastSeen = -1;
	private TwoHopPacket lastSeenPacket = null;

	private InetAddress senderAddr;
	private int numSentBack = 0;
	
	public ServerReceiverThread (String name, int port) {
		numPacketsReceived=0;
		this.name = name;
		try {
			
			socket = new DatagramSocket(port);
			
		} catch (SocketException e1) {
			System.out.println("Socket could not be opened on port " + port);
			e1.printStackTrace();
		} catch (SecurityException e2) {
			System.out.println("Security manager did not allow the conenction to be established");
			e2.printStackTrace();
		}
		System.out.println("Server started. Listening on port " + socket.getLocalPort());
	}
	
	public ServerReceiverThread (String name, int port, String imgPath) {
		this(name,port);
		try {
			gif = new FileOutputStream(new File(imgPath));
		} catch (FileNotFoundException e) {
			System.err.println("Could not create the file " + imgPath);
			e.printStackTrace();
		}
	}
	
	
	public void run(){
		
		

		
		try {
			socket.setSoTimeout(5000);
		} catch (SocketException e1) {
			e1.printStackTrace();
		}
		while (true){
					
			try {
				byte[] buf = new byte[TwoHopPacket.MY_PACKET_LENGTH]; //TODO: get rid of hardcoded number
				DatagramPacket packet = new DatagramPacket(buf, buf.length);
				
				if (socket!=null){
						socket.receive(packet);
					buf = packet.getData();
					
					processData(buf);
					
				}
					
			} catch (SocketTimeoutException e) {
				//System.out.println(name + " timedout. Closing socket");
				//System.out.println("Packets still in missed = " + missedPackets.size());
				//System.out.println("Packets in received list =" + receivedPackets.size());
				try {
					socket.setSoTimeout(3000);
				} catch (SocketException e1) {
					e1.printStackTrace();
				}
				
				if(missedPackets.size()!=0){
					for(Integer i : missedPackets){
						TwoHopPacket missed = new TwoHopPacket(i.intValue());
						DatagramPacket packet = new DatagramPacket(missed.getPacketByte(), missed.getPacketByte().length, senderAddr, lastSeenPacket.getSourcePort());
						try {
							socket.send(packet);
							numSentBack++;
						} catch (IOException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}

					}
				}else if(missedPackets.size()==0 && receivedPackets.size() < lastSeenPacket.getTotalPackets()){
					for(int i = receivedPackets.size(); i < lastSeenPacket.getTotalPackets(); i++){
						TwoHopPacket missed = new TwoHopPacket(i);
						if(!receivedPackets.contains(missed)){
							if(!missedPackets.contains(new Integer(missed.getSequenceNumber()))){
								missedPackets.add(new Integer(missed.getSequenceNumber()));
								DatagramPacket packet = new DatagramPacket(missed.getPacketByte(), missed.getPacketByte().length,  senderAddr, lastSeenPacket.getSourcePort());
								try {
									socket.send(packet);
								} catch (IOException e1) {
									// TODO Auto-generated catch block
									e1.printStackTrace();
								}
								numSentBack++;
							}

						}else{
							if(missedPackets.contains(missed)){
								missedPackets.remove(new Integer(missed.getSequenceNumber()));
							}
						}
						
					}
				}else
					break;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
		
		
		if(gif != null){
			makeGif();
			try {
				gif.close();
			} catch (IOException e) {
				System.err.println("Failed to close the gif");
				e.printStackTrace();
			}
		}

		System.out.println("Received " +numPacketsReceived);
		//System.out.println("Number packets sent back" + numSentBack);

		socket.close();
		
	}

	private void makeGif() {
		Collections.sort(receivedPackets,new TwoHopPacketComparator() );
		for(TwoHopPacket p : receivedPackets){
			processImageData(p.getPacketByte(), TwoHopPacket.MY_PACKET_DATA_LENGTH);
		}
	}

	private void processData(byte[] buf) throws IOException {
		numPacketsReceived++;
		
		// process the header data
		TwoHopPacket thp = new TwoHopPacket(buf);
		lastSeenPacket = thp;
		senderAddr = InetAddress.getByAddress(lastSeenPacket.getSourceAddressByte());
		
		if(thp.getSequenceNumber() == lastSeen + 1){
			lastSeen++;
			if(!receivedPackets.contains(thp)){
				receivedPackets.add(thp);
			}
			
			if(missedPackets.contains(new Integer(thp.getSequenceNumber()))){
				missedPackets.remove(new Integer(thp.getSequenceNumber()));
			}
			
		} else if(thp.getSequenceNumber() < lastSeen){
			if(!receivedPackets.contains(thp)){
				receivedPackets.add(thp);
			}
			if(missedPackets.contains(new Integer(thp.getSequenceNumber()))){
				missedPackets.remove(new Integer(thp.getSequenceNumber()));
			}
		} else if(thp.getSequenceNumber() > lastSeen){
			
			for(int i = lastSeen + 1 ; i < thp.getSequenceNumber(); i++){
				TwoHopPacket missed = new TwoHopPacket(i);
				if(!receivedPackets.contains(missed)){
					if(!missedPackets.contains(new Integer(missed.getSequenceNumber()))){
						missedPackets.add(new Integer(missed.getSequenceNumber()));
						DatagramPacket packet = new DatagramPacket(missed.getPacketByte(), missed.getPacketByte().length,  InetAddress.getByAddress(thp.getSourceAddressByte()), thp.getSourcePort());
						socket.send(packet);
						numSentBack++;
					}

				}else{
					if(missedPackets.contains(missed)){
						missedPackets.remove(new Integer(missed.getSequenceNumber()));
					}
				}
			}
			lastSeen = thp.getSequenceNumber();
			
			if(!receivedPackets.contains(thp)){
				receivedPackets.add(thp);
			}
			
		} else {
			
			System.out.println("*****SHOULD NOT GET HERE");
		}

		//System.out.println(name+ ": received " + numPacketsReceived + " packets");
		//System.out.println(name +": This packet's sequence number is "+ thp.getSequenceNumber() + " sent at " + new Timestamp(thp.getTimeStamp()) + " Total Packet Size " + thp.getTotalPackets());

	}	
	
	private void processImageData(byte[] buf, int length) {
		byte[] imgData = new byte[length];
		System.arraycopy(buf, TwoHopPacket.MY_PACKET_DATA_OFFSET, imgData, 0, imgData.length);
		try {
			gif.write(imgData);
		} catch (IOException e) {
			System.err.println("Failed to write to gif");
			e.printStackTrace();
		}
	}
	
	public class TwoHopPacketComparator implements Comparator<TwoHopPacket>{

		@Override
		public int compare(TwoHopPacket p1, TwoHopPacket p2) {
			
			if(p1.getSequenceNumber() < p2.getSequenceNumber()){
				return -1;
			}else if(p1.getSequenceNumber() > p2.getSequenceNumber()){
				return 1;
			}else{
				return 0;
			}

		}
		
		
	}
	
	
}
